package nayasis.common.db.manager.sql.condition;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import nayasis.common.db.manager.sql.condition.node.BraceNode;
import nayasis.common.db.manager.sql.condition.node.CommandNode;
import nayasis.common.db.manager.sql.condition.node.ConstantNode;
import nayasis.common.db.manager.sql.condition.node.LogicalOperatorNode;
import nayasis.common.db.manager.sql.condition.node.Node;
import nayasis.common.db.manager.sql.condition.node.VariableNode;
import nayasis.common.db.manager.sql.type.Brace;
import nayasis.common.db.manager.sql.type.Command;
import nayasis.common.db.manager.sql.type.LogicalOperator;
import nayasis.common.db.manager.sql.type.Type;
import nayasis.common.exception.unchecked.SyntaxException;
import nayasis.common.util.StringUtil;

public class NodeParser {

    private enum ParseStatus { NONE, QUOT }
    
    public List<Node> parse( String expression ) {
        
        List<Node> result = new ArrayList<>();
        
        if( StringUtil.isEmpty(expression) ) return result;
        
        StringBuffer string = new StringBuffer( expression ).append( ' ' );
        
        StringBuffer token = new StringBuffer();
        
        ParseStatus status = ParseStatus.NONE;
        
        for( int i = 0, iCnt = string.length() - 1; i < iCnt; i++ ) {

            char currC = string.charAt( i );

            if( currC == '\\' && string.charAt( i + 1 ) == '\'' ) {
                token.append( '\'' );
                i += 2;
                continue;
            }
            
            switch( status ) {
                
                case NONE :

                    switch( currC ) {
                        
                        case '\'' :
                            status = ParseStatus.QUOT;
                            break;

                        case ' ' : case '\r' : case '\n' : case '\t' :

                            if( token.length() != 0 ) {
                                result.add( makeNode(token) );
                                token = new StringBuffer();
                            }
                            
                            break;
                            
                        case '(' : case '{' :
                            result.add( new BraceNode( Brace.open ) );
                            break;

                        case ')' : case '}' :
                            
                            if( token.length() > 0 ) {
                                result.add( makeNode(token) );
                                token = new StringBuffer();
                            }
                            
                            result.add( new BraceNode( Brace.close ) );
                            break;
                            
                        default :
                            token.append( currC );
                            
                    }

                    break;

                case QUOT :

                    if( currC == '\'' ) {
                        status = ParseStatus.NONE;
                        continue;
                    }
                    
                    token.append( currC );
                    
                    break;

            }
            
        }
        
        if( status == ParseStatus.QUOT ) {
            throw new SyntaxException( "There is unclosed quot (@)", expression );
        }

        if( token.length() != 0 ) result.add( makeNode(token) );
        
        simplifySingleBraceNode( result );
        
        return result;
        
    }

    private Node makeNode( StringBuffer expr ) {

        Node node = null;
        
        String command = expr.toString().toLowerCase();

        if( command.equals(LogicalOperator.and.toString()) ) {
            node = new LogicalOperatorNode( LogicalOperator.and );
        } else if( command.equals(LogicalOperator.or.toString()) ) {
            node = new LogicalOperatorNode( LogicalOperator.or );
            
        } else if( command.equals(Command.equal.toString()) ) {
            node = new CommandNode( Command.equal );
        } else if( command.equals(Command.greaterEqual.toString()) ) {
            node = new CommandNode( Command.greaterEqual );
        } else if( command.equals(Command.greaterThan.toString()) ) {
            node = new CommandNode( Command.greaterThan );
        } else if( command.equals(Command.isEmpty.toString()) ) {
            node = new CommandNode( Command.isEmpty );
        } else if( command.equals(Command.isNotEmpty.toString()) ) {
            node = new CommandNode( Command.isNotEmpty );
        } else if( command.equals(Command.lessEqual.toString()) ) {
            node = new CommandNode( Command.lessEqual );
        } else if( command.equals(Command.lessThan.toString()) ) {
            node = new CommandNode( Command.lessThan );
        } else if( command.equals(Command.notEqual.toString()) ) {
            node = new CommandNode( Command.notEqual );
            
        } else if( command.charAt(0) == ':' && command.length() > 1 ) {
            node = new VariableNode( command.substring(1) );

        } else {
            node = new ConstantNode( command );
        }

        return node;

    }

    
    private void simplifySingleBraceNode( List<Node> nodes ) {

        Stack<Integer> braceStack = new Stack<>();
        
        for( int i = 0, iCnt = nodes.size(); i < iCnt; i++ ) {
            
            Node node = nodes.get( i );
            
            if( ! node.is(Type.brace) ) continue;

            BraceNode braceNode = (BraceNode) node;

            switch( braceNode.get() ) {
                
                case open  :
                    braceStack.add( i );
                    break;

                case close :
                    
                    int openIndex = braceStack.pop();
                    
                    if( i - openIndex == 2 )  {

                        nodes.remove( i );
                        nodes.remove( openIndex );

                        iCnt =- 2;
                        i =-2;

                    } else if( i - openIndex == 1 ) {

                        nodes.remove( i );
                        nodes.remove( openIndex );
                        nodes.add( i, new ConstantNode( null ) );
                        
                        iCnt =- 1;
                        i =- 1;
                    }

                    break;

            }
            
        }

        if( braceStack.size() > 0 ) {
            throw new SyntaxException( "There is unclosed brace (@)", getString(nodes) );
        }
        
    }

    public String getString( List<Node> nodes ) {

        StringBuffer sb = new StringBuffer();
        
        for( int i = 0, iCnt = nodes.size(); i < iCnt; i++ ) {
            
            Node node = nodes.get( i );
            
            switch( node.getType() ) {
                case brace :
                    sb.append( ((BraceNode) node).toString() );
                    break;
                case command :
                    sb.append( ((CommandNode) node).toString() );
                    break;
                case logicalOperator :
                    sb.append( ((LogicalOperatorNode) node).toString() );
                    break;
                case variable :
                    sb.append( ((VariableNode) node).toString() );
                    break;
                case constant :
                    sb.append( ((ConstantNode) node).toString() );
                    break;
                 
                    
            }
            
            sb.append( " " );
            
        }
        
        return sb.toString();
        
    }
    
}
